import type { IMessage, IUser } from '@rocket.chat/core-typings';

import {
	createRoom,
	getRoomInfo,
	getGroupHistory,
	findRoomMember,
	addUserToRoom,
	addUserToRoomSlashCommand,
} from '../../../../../apps/meteor/tests/data/rooms.helper';
import { type IRequestConfig, getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper';
import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants';
import { federationConfig } from '../helper/config';
import { createDDPListener } from '../helper/ddp-listener';
import { SynapseClient } from '../helper/synapse-client';

// import { KnownMembership } from 'matrix-js-sdk';
// import { t } from 'i18next';

(IS_EE ? describe : describe.skip)('Federation', () => {
	let rc1AdminRequestConfig: IRequestConfig;
	let rc1User1RequestConfig: IRequestConfig;
	let hs1AdminApp: SynapseClient;
	let hs1User1App: SynapseClient;

	beforeAll(async () => {
		// Create admin request config for RC1
		rc1AdminRequestConfig = await getRequestConfig(
			federationConfig.rc1.apiUrl,
			federationConfig.rc1.adminUser,
			federationConfig.rc1.adminPassword,
		);

		// Create user1 in RC1 using federation config values
		await createUser(
			{
				username: federationConfig.rc1.additionalUser1.username,
				password: federationConfig.rc1.additionalUser1.password,
				email: `${federationConfig.rc1.additionalUser1.username}@rocket.chat`,
				name: federationConfig.rc1.additionalUser1.username,
			},
			rc1AdminRequestConfig,
		);

		// Create user1 request config for RC1
		rc1User1RequestConfig = await getRequestConfig(
			federationConfig.rc1.apiUrl,
			federationConfig.rc1.additionalUser1.username,
			federationConfig.rc1.additionalUser1.password,
		);

		// Create admin Synapse client for HS1
		hs1AdminApp = new SynapseClient(federationConfig.hs1.url, federationConfig.hs1.adminUser, federationConfig.hs1.adminPassword);
		await hs1AdminApp.initialize();

		// Create user1 Synapse client for HS1
		hs1User1App = new SynapseClient(
			federationConfig.hs1.url,
			federationConfig.hs1.additionalUser1.matrixUserId,
			federationConfig.hs1.additionalUser1.password,
		);
		await hs1User1App.initialize();
	});

	afterAll(async () => {
		if (hs1AdminApp) {
			await hs1AdminApp.close();
		}
		if (hs1User1App) {
			await hs1User1App.close();
		}
	});

	describe('Rooms', () => {
		describe('Create direct message rooms', () => {
			// Creating a fresh user for this test suite to avoid collisions,
			// since DMs are unique for each user pair
			let userRequestConfig: IRequestConfig;
			let createdUser: IUser;
			beforeAll(async () => {
				const user = { username: `user-${Date.now()}`, password: '123' };
				createdUser = await createUser(user, rc1AdminRequestConfig);
				userRequestConfig = await getRequestConfig(federationConfig.rc1.apiUrl, user.username, user.password);
			});

			afterAll(async () => {
				await deleteUser(createdUser, {}, rc1AdminRequestConfig);
			});

			it('It should create a federated room when federated members are added', async () => {
				const response = await createRoom({
					type: 'd',
					username: federationConfig.hs1.adminMatrixUserId,
					config: userRequestConfig,
				});

				expect(response.status).toBe(200);
				expect(response.body).toHaveProperty('success', true);
				expect(response.body).toHaveProperty('room');
				expect(response.body.room).toHaveProperty('_id');
				expect(response.body.room).toHaveProperty('t', 'd');

				const roomInfo = await getRoomInfo(response.body.room._id, userRequestConfig);
				expect(roomInfo.room).toHaveProperty('federated', true);
			});

			it('It should create a non-federated room when only local members are added', async () => {
				const response = await createRoom({
					type: 'd',
					username: federationConfig.rc1.additionalUser1.username,
					config: userRequestConfig,
				});

				expect(response.status).toBe(200);
				expect(response.body).toHaveProperty('success', true);
				expect(response.body).toHaveProperty('room');
				expect(response.body.room).toHaveProperty('_id');
				expect(response.body.room).toHaveProperty('t', 'd');

				const roomInfo = await getRoomInfo(response.body.room._id, userRequestConfig);
				expect(roomInfo.room).not.toHaveProperty('federated');
			});
		});

		describe('Create a room on RC as private, explicitly not federated, with federated users in creation modal', () => {
			describe('Add 1 federated user in the creation modal', () => {
				it('It should not allow the creation of the room', async () => {
					const channelName = `non-federated-channel-single-fed-${Date.now()}`;

					// RC view: Attempt to create a non-federated private room with 1 federated user
					const response = await createRoom({
						type: 'p',
						name: channelName,
						members: [federationConfig.hs1.adminMatrixUserId],
						extraData: {
							federated: false,
						},
						config: rc1AdminRequestConfig,
					});

					// RC view: Verify the room creation failed
					expect(response.status).toBe(400);
					expect(response.body).toHaveProperty('success', false);
					expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
				});
			});

			describe('Add 2 federated users in the creation modal', () => {
				it('It should not allow the creation of the room', async () => {
					const channelName = `non-federated-channel-multi-fed-${Date.now()}`;

					// RC view: Attempt to create a non-federated private room with 2 federated users
					const response = await createRoom({
						type: 'p',
						name: channelName,
						members: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
						extraData: {
							federated: false,
						},
						config: rc1AdminRequestConfig,
					});

					// RC view: Verify the room creation failed
					expect(response.status).toBe(400);
					expect(response.body).toHaveProperty('success', false);
					expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
				});
			});

			describe('Add 1 federated user and 1 local user in the creation modal', () => {
				it('It should not allow the creation of the room', async () => {
					const channelName = `non-federated-channel-mixed-${Date.now()}`;

					// RC view: Attempt to create a non-federated private room with 1 federated user and 1 local user
					const response = await createRoom({
						type: 'p',
						name: channelName,
						members: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
						extraData: {
							federated: false,
						},
						config: rc1AdminRequestConfig,
					});

					// RC view: Verify the room creation failed
					expect(response.status).toBe(400);
					expect(response.body).toHaveProperty('success', false);
					expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
				});
			});
		});

		describe('Create a room on RC as private, do not mark as federated and', () => {
			let nonFederatedChannel: { _id: string; name: string; t: string; federated?: boolean };

			beforeEach(async () => {
				const channelName = `non-federated-channel-${Date.now()}`;

				// Create a non-federated private room (without federated members)
				const createResponse = await createRoom({
					type: 'p',
					name: channelName,
					members: [],
					extraData: {
						federated: false,
					},
					config: rc1AdminRequestConfig,
				});

				nonFederatedChannel = createResponse.body.group;

				expect(nonFederatedChannel).toHaveProperty('_id');
				expect(nonFederatedChannel).toHaveProperty('name', channelName);
				expect(nonFederatedChannel).toHaveProperty('t', 'p');
				expect(nonFederatedChannel).not.toHaveProperty('federated', true);
			}, 10000);

			// No cleanup needed - rooms are left for debugging purposes

			describe('Go to the members list and try to add a federated user', () => {
				it('It should not allow and show an error message', async () => {
					// RC view: Attempt to add a federated user to the non-federated room
					const response = await addUserToRoom({
						usernames: [federationConfig.hs1.adminMatrixUserId],
						rid: nonFederatedChannel._id,
						config: rc1AdminRequestConfig,
					});

					expect(response.body).toHaveProperty('success', true);
					expect(response.body).toHaveProperty('message');

					// Parse the error message from the DDP response
					const messageData = JSON.parse(response.body.message);
					expect(messageData).toHaveProperty('error');
					expect(messageData.error).toHaveProperty('error', 'error-federated-users-in-non-federated-rooms');

					// RC view: Verify the federated user was NOT added to the room's member list
					const federatedUserInRoom = await findRoomMember(
						nonFederatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{ initialDelay: 0 },
						rc1AdminRequestConfig,
					);
					expect(federatedUserInRoom).toBeNull();

					// RC view: Verify room remains non-federated
					const roomInfo = await getRoomInfo(nonFederatedChannel._id, rc1AdminRequestConfig);
					expect(roomInfo.room).not.toHaveProperty('federated', true);
				});
			});

			describe('Go to the composer and use the /invite slash command to add a federated user', () => {
				it('It should not allow and show an error message', async () => {
					// Set up DDP listener to catch ephemeral messages
					const ddpListener = createDDPListener(federationConfig.rc1.apiUrl, rc1AdminRequestConfig);

					// Connect to DDP and subscribe to ephemeral messages
					await ddpListener.connect();

					// RC view: Execute the /invite slash command to add a federated user
					const response = await addUserToRoomSlashCommand({
						usernames: [federationConfig.hs1.adminMatrixUserId],
						rid: nonFederatedChannel._id,
						config: rc1AdminRequestConfig,
					});

					// The slash command returns success but broadcasts ephemeral messages
					// instead of throwing errors
					expect(response.body).toHaveProperty('success', true);
					expect(response.body).toHaveProperty('message');
					const messageData = JSON.parse(response.body.message);
					expect(messageData).toHaveProperty('msg', 'result');

					// Wait for the ephemeral message to be broadcast
					const ephemeralMessage = await ddpListener.waitForEphemeralMessage(
						'You cannot add external users to a non-federated room',
						5000, // 5 second timeout
						nonFederatedChannel._id,
					);

					// Verify the ephemeral message content
					expect(ephemeralMessage.msg).toContain('You cannot add external users to a non-federated room');
					expect(ephemeralMessage.u.username).toBe('rocket.cat');
					expect(ephemeralMessage.private).toBe(true);
					expect(ephemeralMessage.rid).toBe(nonFederatedChannel._id); // Verify it's for the correct room

					// RC view: Verify the federated user was NOT added to the room's member list
					const federatedUserInRoom = await findRoomMember(
						nonFederatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{},
						rc1AdminRequestConfig,
					);
					expect(federatedUserInRoom).toBeNull();

					// RC view: Verify room remains non-federated
					const roomInfo = await getRoomInfo(nonFederatedChannel._id, rc1AdminRequestConfig);
					expect(roomInfo.room).not.toHaveProperty('federated', true);

					ddpListener.disconnect();
				});
			});
		});

		describe('Create a room on RC as private and federated and', () => {
			describe('Add 1 federated user in the creation modal', () => {
				let channelName: string;
				let federatedChannel: any;

				beforeAll(async () => {
					channelName = `federated-channel-${Date.now()}`;

					const createResponse = await createRoom({
						type: 'p',
						name: channelName,
						members: [federationConfig.hs1.adminMatrixUserId],
						extraData: {
							federated: true,
						},
						config: rc1AdminRequestConfig,
					});

					// For private groups, the response has 'group' property, not 'channel'
					federatedChannel = createResponse.body.group;

					expect(federatedChannel).toHaveProperty('_id');
					expect(federatedChannel).toHaveProperty('name', channelName);
					expect(federatedChannel).toHaveProperty('t', 'p');
					expect(federatedChannel).toHaveProperty('federated', true);
					expect(federatedChannel).toHaveProperty('federation');
					expect((federatedChannel as any).federation).toHaveProperty('version', 1);

					const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
					expect(acceptedRoomId).not.toBe('');

					// TODO: Figure out why syncing events are not working and uncomment this when we get the state change from
					// invite to join
					// const joinedRoomId = await this.hs1App.getRoomIdByRoomNameAndMembership(channelName, KnownMembership.Join);
					// expect(acceptedRoomId, 'Expected to have joined the room, but joinedRoomId is different from acceptedRoomId').to.equal(joinedRoomId);
				}, 10000);

				it('It should show the room on the remote Element or RC', async () => {
					// RC view: Check in RC
					const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
					expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
					expect(roomInfo.room).toHaveProperty('federated', true);

					// Synapse view: Check in Element
					const elementRoom = hs1AdminApp.getRoom(channelName);
					expect(elementRoom).toHaveProperty('name', channelName);
				});

				it('It should show the new user in the members list', async () => {
					// RC view: Check in RC that the federated user is in the members list
					const rc1AdminUserInRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.rc1.adminUser,
						{ initialDelay: 0 },
						rc1AdminRequestConfig,
					);
					const hs1AdminUserInRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{ initialDelay: 0 },
						rc1AdminRequestConfig,
					);

					expect(rc1AdminUserInRC).not.toBeNull();
					expect(hs1AdminUserInRC).not.toBeNull();
					expect(hs1AdminUserInRC?.federated).toBe(true);

					// Synapse view: Check in Element (Matrix) that the federated user is in the members list
					const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
						delay: 2000,
					});
					const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
						delay: 2000,
					});
					expect(rc1AdminUserInSynapse).not.toBeNull();
					expect(hs1AdminUserInSynapse).not.toBeNull();
				});

				it('It should show the system message that the user added', async () => {
					// RC view: Check in RC. We don't check in Synapse because this is not part of the protocol
					// Get the room history to find the system message
					const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
					expect(Array.isArray(historyResponse.messages)).toBe(true);

					// Look for a system message about the user joining
					// System messages typically have t: 'uj' (user joined) and the msg contains the username
					const joinMessage = historyResponse.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
					);

					expect(joinMessage).toBeDefined();
					expect(joinMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					expect(joinMessage?.u?.username).toBe(federationConfig.hs1.adminMatrixUserId);
				});
			});

			describe('Add 2 or more federated users in the creation modal', () => {
				let channelName: string;
				let federatedChannel: any;

				beforeAll(async () => {
					channelName = `federated-channel-multi-${Date.now()}`;

					// Create room with both federated users
					const createResponse = await createRoom({
						type: 'p',
						name: channelName,
						members: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
						extraData: {
							federated: true,
						},
						config: rc1AdminRequestConfig,
					});

					// For private groups, the response has 'group' property, not 'channel'
					federatedChannel = createResponse.body.group;

					expect(federatedChannel).toHaveProperty('_id');
					expect(federatedChannel).toHaveProperty('name', channelName);
					expect(federatedChannel).toHaveProperty('t', 'p');
					expect(federatedChannel).toHaveProperty('federated', true);
					expect(federatedChannel).toHaveProperty('federation');
					expect((federatedChannel as any).federation).toHaveProperty('version', 1);

					// Accept invitations for both users
					const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
					expect(acceptedRoomId1).not.toBe('');

					const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
					expect(acceptedRoomId2).not.toBe('');

					// TODO: Figure out why syncing events are not working and uncomment this when we get the state change from
					// invite to join
					// const joinedRoomId = await this.hs1App.getRoomIdByRoomNameAndMembership(channelName, KnownMembership.Join);
					// expect(acceptedRoomId, 'Expected to have joined the room, but joinedRoomId is different from acceptedRoomId').to.equal(joinedRoomId);
				}, 15000);

				it('It should show the room on all the involved remote Element or RC', async () => {
					// RC view: Check in RC
					const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
					expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
					expect(roomInfo.room).toHaveProperty('federated', true);

					// Synapse view: Check in Element for admin user
					const elementRoom1 = hs1AdminApp.getRoom(channelName);
					expect(elementRoom1).toHaveProperty('name', channelName);

					// Synapse view: Check in Element for user1
					const elementRoom2 = hs1User1App.getRoom(channelName);
					expect(elementRoom2).toHaveProperty('name', channelName);
				});

				it('It should show the new users in the members list of all RCs involved', async () => {
					// RC view: Check in RC that both federated users are in the members list
					const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
					const hs1AdminUserInRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{},
						rc1AdminRequestConfig,
					);
					const hs1User1InRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.hs1.additionalUser1.matrixUserId,
						{},
						rc1AdminRequestConfig,
					);

					expect(rc1AdminUserInRC).not.toBeNull();
					expect(hs1AdminUserInRC).not.toBeNull();
					expect(hs1User1InRC).not.toBeNull();
					expect(hs1AdminUserInRC?.federated).toBe(true);
					expect(hs1User1InRC?.federated).toBe(true);

					// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
					const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
					const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
					const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId, {
						initialDelay: 2000,
					});

					expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
					expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
					expect(hs1User1InSynapseAdmin).not.toBeNull();

					// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
					const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
					const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
					const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);

					expect(rc1AdminUserInSynapseUser1).not.toBeNull();
					expect(hs1AdminUserInSynapseUser1).not.toBeNull();
					expect(hs1User1InSynapseUser1).not.toBeNull();
				});

				it('It should show the system messages that the user added on all RCs involved', async () => {
					// RC view: Check in RC. We don't check in Synapse because this is not part of the protocol
					// Get the room history to find the system messages
					const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
					expect(Array.isArray(historyResponse.messages)).toBe(true);

					// Look for system messages about both users joining
					// System messages typically have t: 'uj' (user joined) and the msg contains the username
					const adminJoinMessage = historyResponse.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
					);

					const hs1User1JoinMessage = historyResponse.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.hs1.additionalUser1.matrixUserId,
					);

					expect(adminJoinMessage).toBeDefined();
					expect(adminJoinMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					expect(adminJoinMessage?.u?.username).toBe(federationConfig.hs1.adminMatrixUserId);

					expect(hs1User1JoinMessage).toBeDefined();
					expect(hs1User1JoinMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
					expect(hs1User1JoinMessage?.u?.username).toBe(federationConfig.hs1.additionalUser1.matrixUserId);
				});
			});

			describe('Add 1 federated user and 1 local user in the creation modal', () => {
				let channelName: string;
				let federatedChannel: any;

				beforeAll(async () => {
					channelName = `federated-channel-mixed-${Date.now()}`;

					// Create room with 1 federated user (from Synapse) and 1 local user (from RC)
					const createResponse = await createRoom({
						type: 'p',
						name: channelName,
						members: [
							federationConfig.hs1.adminMatrixUserId, // federated user
							federationConfig.rc1.additionalUser1.username, // local user
						],
						extraData: {
							federated: true,
						},
						config: rc1AdminRequestConfig,
					});

					// For private groups, the response has 'group' property, not 'channel'
					federatedChannel = createResponse.body.group;

					expect(federatedChannel).toHaveProperty('_id');
					expect(federatedChannel).toHaveProperty('name', channelName);
					expect(federatedChannel).toHaveProperty('t', 'p');
					expect(federatedChannel).toHaveProperty('federated', true);
					expect(federatedChannel).toHaveProperty('federation');
					expect((federatedChannel as any).federation).toHaveProperty('version', 1);

					// Accept invitation for the federated user (local user is already added automatically)
					const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
					expect(acceptedRoomId).not.toBe('');
				}, 15000);

				it('It should show the room on the remote Element or RC and local for the second user', async () => {
					// RC view: Check in RC (admin view)
					const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
					expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
					expect(roomInfo.room).toHaveProperty('federated', true);

					// RC view: Check in RC (user1 view - local user)
					const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
					expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
					expect(roomInfoUser1.room).toHaveProperty('federated', true);

					// Synapse view: Check in Synapse (Matrix) for federated user
					const room = hs1AdminApp.getRoom(channelName);
					expect(room).toHaveProperty('name', channelName);
					expect(room.getMyMembership()).toBe('join');
				});

				it('It should show the 2 new users in the members list', async () => {
					// RC view: Check in RC (admin view) that both users are in the members list
					const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
					const rc1User1InRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.rc1.additionalUser1.username,
						{},
						rc1AdminRequestConfig,
					);
					const hs1AdminUserInRC = await findRoomMember(
						federatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{},
						rc1AdminRequestConfig,
					);

					expect(rc1AdminUserInRC).not.toBeNull();
					expect(rc1User1InRC).not.toBeNull();
					expect(hs1AdminUserInRC).not.toBeNull();
					expect(hs1AdminUserInRC?.federated).toBe(true);

					// RC view: Check in RC (user1 view) that both users are in the members list
					const rc1AdminUserInRCUser1 = await findRoomMember(
						federatedChannel._id,
						federationConfig.rc1.adminUser,
						{},
						rc1User1RequestConfig,
					);
					const rc1User1InRCUser1 = await findRoomMember(
						federatedChannel._id,
						federationConfig.rc1.additionalUser1.username,
						{},
						rc1User1RequestConfig,
					);
					const hs1AdminUserInRCUser1 = await findRoomMember(
						federatedChannel._id,
						federationConfig.hs1.adminMatrixUserId,
						{},
						rc1User1RequestConfig,
					);

					expect(rc1AdminUserInRCUser1).not.toBeNull();
					expect(rc1User1InRCUser1).not.toBeNull();
					expect(hs1AdminUserInRCUser1).not.toBeNull();
					expect(hs1AdminUserInRCUser1?.federated).toBe(true);

					// Synapse view: Check in Synapse (Matrix) that both users are in the members list
					const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
						initialDelay: 2000,
					});
					const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
						initialDelay: 2000,
					});
					const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
						initialDelay: 2000,
					});

					expect(rc1AdminUserInSynapse).not.toBeNull();
					expect(rc1User1InSynapse).not.toBeNull();
					expect(hs1AdminUserInSynapse).not.toBeNull();
				});

				it('It should show the 2 system messages that the user added', async () => {
					// RC view: Check in RC (admin view) for system messages about both users joining
					const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
					expect(Array.isArray(historyResponse.messages)).toBe(true);

					// Look for system messages about both users joining
					const localUserJoinMessage = historyResponse.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.rc1.additionalUser1.username,
					);

					const federatedUserJoinMessage = historyResponse.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
					);

					expect(localUserJoinMessage).toBeDefined();
					expect(localUserJoinMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);
					expect(localUserJoinMessage?.u?.username).toBe(federationConfig.rc1.additionalUser1.username);

					expect(federatedUserJoinMessage).toBeDefined();
					expect(federatedUserJoinMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					expect(federatedUserJoinMessage?.u?.username).toBe(federationConfig.hs1.adminMatrixUserId);

					// RC view: Check in RC (user1 view) for system messages about both users joining
					const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
					expect(Array.isArray(historyResponseUser1.messages)).toBe(true);

					const localUserJoinMessageUser1 = historyResponseUser1.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.rc1.additionalUser1.username,
					);

					const federatedUserJoinMessageUser1 = historyResponseUser1.messages.find(
						(message: IMessage) => message.t === 'uj' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
					);

					expect(localUserJoinMessageUser1).toBeDefined();
					expect(federatedUserJoinMessageUser1).toBeDefined();
				});
			});
		});

		describe('Create a room on RC as private and federated, then invite users', () => {
			describe('Go to the members list and', () => {
				describe('Add a federated user', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-invite-single-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add federated user to the room
						const addUserResponse = await addUserToRoom({
							usernames: [federationConfig.hs1.adminMatrixUserId],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitation for the federated user
						const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId).not.toBe('');
					}, 15000);

					it('It should show the room on the remote Element or RC', async () => {
						// RC view: Check in RC
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// Synapse view: Check in Element
						const elementRoom = hs1AdminApp.getRoom(channelName);
						expect(elementRoom).toHaveProperty('name', channelName);
					});

					it('It should show the new user in the members list', async () => {
						// RC view: Check in RC that both users are in the members list
						const rc1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.adminUser,
							{ initialDelay: 0 },
							rc1AdminRequestConfig,
						);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{ initialDelay: 0 },
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);

						// Synapse view: Check in Element (Matrix) that both users are in the members list
						const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
							delay: 2000,
						});
						const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
							delay: 2000,
						});
						expect(rc1AdminUserInSynapse).not.toBeNull();
						expect(hs1AdminUserInSynapse).not.toBeNull();
					});

					it('It should show the system message that the user added', async () => {
						// RC view: Check in RC
						// Get the room history to find the system messages
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// Look for system messages about the user being added
						// look for 'au' (added user) message types
						const addedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(addedMessage).toBeDefined();
						expect(addedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					});
				});

				describe('Add 2 or more federated users at the same time', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-invite-multi-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add both federated users to the room
						const addUserResponse = await addUserToRoom({
							usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitations for both users
						const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId1).not.toBe('');

						const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId2).not.toBe('');
					}, 15000);

					it('It should show the room on all the involved remote Element or RC', async () => {
						// RC view: Check in RC
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// Synapse view: Check in Element for admin user
						const elementRoom1 = hs1AdminApp.getRoom(channelName);
						expect(elementRoom1).toHaveProperty('name', channelName);

						// Synapse view: Check in Element for user1
						const elementRoom2 = hs1User1App.getRoom(channelName);
						expect(elementRoom2).toHaveProperty('name', channelName);
					});

					it('It should show the new users in the members list of all RCs involved', async () => {
						// RC view: Check in RC that all users are in the members list
						const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1AdminRequestConfig,
						);
						const hs1User1InRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.additionalUser1.matrixUserId,
							{},
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1User1InRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);
						expect(hs1User1InRC?.federated).toBe(true);

						// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
						const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
						const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
						const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(
							channelName,
							federationConfig.hs1.additionalUser1.matrixUserId,
							{
								initialDelay: 2000,
							},
						);

						expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
						expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
						expect(hs1User1InSynapseAdmin).not.toBeNull();

						// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
						const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
						const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
						const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);

						expect(rc1AdminUserInSynapseUser1).not.toBeNull();
						expect(hs1AdminUserInSynapseUser1).not.toBeNull();
						expect(hs1User1InSynapseUser1).not.toBeNull();
					});

					it('It should show the system messages that the user added on all RCs involved', async () => {
						// RC view: Check in RC
						// Get the room history to find the system messages
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// Look for system messages about both users being added
						// 'au' (added user) message types
						const adminAddedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(adminAddedMessage).toBeDefined();
						expect(adminAddedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);

						// Look for 'au' (added user) message types
						const hs1User1AddedMessage = historyResponse.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.additionalUser1.matrixUserId),
						);

						expect(hs1User1AddedMessage).toBeDefined();
						expect(hs1User1AddedMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
					});
				});

				describe('Add 1 federated user and 1 local user at the same time', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-invite-mixed-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add 1 federated user and 1 local user to the room
						const addUserResponse = await addUserToRoom({
							usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitation for the federated user (local user is added automatically)
						const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId).not.toBe('');
					}, 15000);

					it('It should show the room on the remote Element or RC and local for the second user', async () => {
						// RC view: Check in RC (admin view)
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// RC view: Check in RC (user1 view - local user)
						const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
						expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfoUser1.room).toHaveProperty('federated', true);

						// Synapse view: Check in Synapse (Matrix) for federated user
						const room = hs1AdminApp.getRoom(channelName);
						expect(room).toHaveProperty('name', channelName);
						expect(room.getMyMembership()).toBe('join');
					});

					it('It should show the 2 new users in the members list', async () => {
						// RC view: Check in RC (admin view) that all users are in the members list
						const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
						const rc1User1InRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.additionalUser1.username,
							{},
							rc1AdminRequestConfig,
						);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(rc1User1InRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);

						// RC view: Check in RC (user1 view) that all users are in the members list
						const rc1AdminUserInRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.adminUser,
							{},
							rc1User1RequestConfig,
						);
						const rc1User1InRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.additionalUser1.username,
							{},
							rc1User1RequestConfig,
						);
						const hs1AdminUserInRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1User1RequestConfig,
						);

						expect(rc1AdminUserInRCUser1).not.toBeNull();
						expect(rc1User1InRCUser1).not.toBeNull();
						expect(hs1AdminUserInRCUser1).not.toBeNull();
						expect(hs1AdminUserInRCUser1?.federated).toBe(true);

						// Synapse view: Check in Synapse (Matrix) that all users are in the members list
						const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
							initialDelay: 2000,
						});
						const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
							initialDelay: 2000,
						});
						const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
							initialDelay: 2000,
						});

						expect(rc1AdminUserInSynapse).not.toBeNull();
						expect(rc1User1InSynapse).not.toBeNull();
						expect(hs1AdminUserInSynapse).not.toBeNull();
					});

					it('It should show the 2 system messages that the user added', async () => {
						// RC view: Check in RC (admin view) for system messages about both users joining
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// 'au' (added user) message types
						const localUserAddedMessage = historyResponse.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
						);

						expect(localUserAddedMessage).toBeDefined();
						expect(localUserAddedMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);

						const federatedUserAddedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(federatedUserAddedMessage).toBeDefined();
						expect(federatedUserAddedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);

						// RC view: Check in RC (user1 view) for system messages about both users being added
						const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
						expect(Array.isArray(historyResponseUser1.messages)).toBe(true);

						// Look for 'au' (added user) message types
						const localUserAddedMessageUser1 = historyResponseUser1.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
						);

						expect(localUserAddedMessageUser1).toBeDefined();
						expect(localUserAddedMessageUser1?.msg).toContain(federationConfig.rc1.additionalUser1.username);

						const federatedUserAddedMessageUser1 = historyResponseUser1.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(federatedUserAddedMessageUser1).toBeDefined();
						expect(federatedUserAddedMessageUser1?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					});
				});
			});

			describe('Go to the composer and use the /invite slash command to', () => {
				describe('Add a federated user', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-slash-single-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add federated user to the room using slash command
						const addUserResponse = await addUserToRoomSlashCommand({
							usernames: [federationConfig.hs1.adminMatrixUserId],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitation for the federated user
						const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId).not.toBe('');
					}, 15000);

					it('It should show the room on the remote Element or RC', async () => {
						// RC view: Check in RC
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// Synapse view: Check in Element
						const elementRoom = hs1AdminApp.getRoom(channelName);
						expect(elementRoom).toHaveProperty('name', channelName);
					});

					it('It should show the new user in the members list', async () => {
						// RC view: Check in RC that both users are in the members list
						const rc1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.adminUser,
							{ initialDelay: 0 },
							rc1AdminRequestConfig,
						);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{ initialDelay: 0 },
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);

						// Synapse view: Check in Element (Matrix) that both users are in the members list
						const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
							delay: 2000,
						});
						const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
							delay: 2000,
						});
						expect(rc1AdminUserInSynapse).not.toBeNull();
						expect(hs1AdminUserInSynapse).not.toBeNull();
					});

					it('It should show the system message that the user added', async () => {
						// RC view: Check in RC
						// Get the room history to find the system messages
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// Look for system messages about the user being added
						// 'au' (added user) message types
						const addedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(addedMessage).toBeDefined();
						expect(addedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					});
				});

				describe('Add 2 or more federated users at the same time', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-slash-multi-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add both federated users to the room using slash command
						const addUserResponse = await addUserToRoomSlashCommand({
							usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitations for both users
						const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId1).not.toBe('');

						const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId2).not.toBe('');
					}, 15000);

					it('It should show the room on all the involved remote Element or RC', async () => {
						// RC view: Check in RC
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// Synapse view: Check in Element for admin user
						const elementRoom1 = hs1AdminApp.getRoom(channelName);
						expect(elementRoom1).toHaveProperty('name', channelName);

						// Synapse view: Check in Element for user1
						const elementRoom2 = hs1User1App.getRoom(channelName);
						expect(elementRoom2).toHaveProperty('name', channelName);
					});

					it('It should show the new users in the members list of all RCs involved', async () => {
						// RC view: Check in RC that all users are in the members list
						const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1AdminRequestConfig,
						);
						const hs1User1InRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.additionalUser1.matrixUserId,
							{},
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1User1InRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);
						expect(hs1User1InRC?.federated).toBe(true);

						// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
						const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
						const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
						const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(
							channelName,
							federationConfig.hs1.additionalUser1.matrixUserId,
							{
								initialDelay: 2000,
							},
						);

						expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
						expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
						expect(hs1User1InSynapseAdmin).not.toBeNull();

						// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
						const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
						const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
						const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);

						expect(rc1AdminUserInSynapseUser1).not.toBeNull();
						expect(hs1AdminUserInSynapseUser1).not.toBeNull();
						expect(hs1User1InSynapseUser1).not.toBeNull();
					});

					it('It should show the system messages that the user added on all RCs involved', async () => {
						// RC view: Check in RC
						// Get the room history to find the system messages
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// Look for system messages about both users being added
						// 'au' (added user) message types
						const adminAddedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(adminAddedMessage).toBeDefined();
						expect(adminAddedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);

						const hs1User1AddedMessage = historyResponse.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.additionalUser1.matrixUserId),
						);

						expect(hs1User1AddedMessage).toBeDefined();
						expect(hs1User1AddedMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
					});
				});

				describe('Add 1 federated user and 1 local user at the same time', () => {
					let channelName: string;
					let federatedChannel: any;

					beforeAll(async () => {
						channelName = `federated-channel-slash-mixed-${Date.now()}`;

						// Create empty federated room without members
						const createResponse = await createRoom({
							type: 'p',
							name: channelName,
							members: [],
							extraData: {
								federated: true,
							},
							config: rc1AdminRequestConfig,
						});

						federatedChannel = createResponse.body.group;

						expect(federatedChannel).toHaveProperty('_id');
						expect(federatedChannel).toHaveProperty('name', channelName);
						expect(federatedChannel).toHaveProperty('t', 'p');
						expect(federatedChannel).toHaveProperty('federated', true);
						expect(federatedChannel).toHaveProperty('federation');
						expect((federatedChannel as any).federation).toHaveProperty('version', 1);

						// Wait for federation setup to complete (Matrix room creation and mrid assignment)
						// This ensures the room.federation.mrid field is properly set before adding users
						await new Promise((resolve) => setTimeout(resolve, 2000));

						// Add 1 federated user and 1 local user to the room using slash command
						const addUserResponse = await addUserToRoomSlashCommand({
							usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
							rid: federatedChannel._id,
							config: rc1AdminRequestConfig,
						});

						expect(addUserResponse.body).toHaveProperty('success', true);

						// Accept invitation for the federated user (local user is added automatically)
						const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
						expect(acceptedRoomId).not.toBe('');
					}, 15000);

					it('It should show the room on the remote Element or RC and local for the second user', async () => {
						// RC view: Check in RC (admin view)
						const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
						expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfo.room).toHaveProperty('federated', true);

						// RC view: Check in RC (user1 view - local user)
						const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
						expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
						expect(roomInfoUser1.room).toHaveProperty('federated', true);

						// Synapse view: Check in Synapse (Matrix) for federated user
						const room = hs1AdminApp.getRoom(channelName);
						expect(room).toHaveProperty('name', channelName);
						expect(room.getMyMembership()).toBe('join');
					});

					it('It should show the 2 new users in the members list', async () => {
						// RC view: Check in RC (admin view) that all users are in the members list
						const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
						const rc1User1InRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.additionalUser1.username,
							{},
							rc1AdminRequestConfig,
						);
						const hs1AdminUserInRC = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1AdminRequestConfig,
						);

						expect(rc1AdminUserInRC).not.toBeNull();
						expect(rc1User1InRC).not.toBeNull();
						expect(hs1AdminUserInRC).not.toBeNull();
						expect(hs1AdminUserInRC?.federated).toBe(true);

						// RC view: Check in RC (user1 view) that all users are in the members list
						const rc1AdminUserInRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.adminUser,
							{},
							rc1User1RequestConfig,
						);
						const rc1User1InRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.rc1.additionalUser1.username,
							{},
							rc1User1RequestConfig,
						);
						const hs1AdminUserInRCUser1 = await findRoomMember(
							federatedChannel._id,
							federationConfig.hs1.adminMatrixUserId,
							{},
							rc1User1RequestConfig,
						);

						expect(rc1AdminUserInRCUser1).not.toBeNull();
						expect(rc1User1InRCUser1).not.toBeNull();
						expect(hs1AdminUserInRCUser1).not.toBeNull();
						expect(hs1AdminUserInRCUser1?.federated).toBe(true);

						// Synapse view: Check in Synapse (Matrix) that all users are in the members list
						const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
							initialDelay: 2000,
						});
						const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
							initialDelay: 2000,
						});
						const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
							initialDelay: 2000,
						});

						expect(rc1AdminUserInSynapse).not.toBeNull();
						expect(rc1User1InSynapse).not.toBeNull();
						expect(hs1AdminUserInSynapse).not.toBeNull();
					});

					it('It should show the 2 system messages that the user added', async () => {
						// RC view: Check in RC (admin view) for system messages about both users joining
						const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
						expect(Array.isArray(historyResponse.messages)).toBe(true);

						// Look for system messages about both users joining
						// 'au' (added user) message types
						const localUserAddedMessage = historyResponse.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
						);

						expect(localUserAddedMessage).toBeDefined();
						expect(localUserAddedMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);

						const federatedUserAddedMessage = historyResponse.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(federatedUserAddedMessage).toBeDefined();
						expect(federatedUserAddedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);

						// RC view: Check in RC (user1 view) for system messages about both users being added
						const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
						expect(Array.isArray(historyResponseUser1.messages)).toBe(true);

						// Look for 'au' (added user) message types
						const localUserAddedMessageUser1 = historyResponseUser1.messages.find(
							(message: IMessage) =>
								message.t === 'au' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
						);

						const federatedUserAddedMessageUser1 = historyResponseUser1.messages.find(
							(message: IMessage) => message.t === 'au' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
						);

						expect(localUserAddedMessageUser1).toBeDefined();
						expect(localUserAddedMessageUser1?.msg).toContain(federationConfig.rc1.additionalUser1.username);

						expect(federatedUserAddedMessageUser1).toBeDefined();
						expect(federatedUserAddedMessageUser1?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
					});
				});
			});
		});
	});
});
